﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Red.CommUtils;
using Red.CommUtils.Application;
using Red.Core.Network;
using Red.Core.Network.PacketNetwork;

namespace Red.Scripts.Defines.Scc
{
    public class Connection : Log.Emitter
    {
        #region Types

        public enum Result
        {
            ChannelFailure,
            VersionFailure,
            Success
        }

        public delegate void MessageHandler( comm.Message message );

        #endregion Types

        #region Private Data

        private PacketNetworkManager _netMan;
        private ChannelFactory _channelFactory;
        private IChannel _channel;

        #endregion Private Data

        #region Public Consts

        public readonly Version RequiredSccVersion = new Version( 1, 1, 8 );

        public const int SccCommandIdIsReady = 2;
        public const int SccCommandIdVersion = 4;

        #endregion Public Consts

        #region Public Properties

        public int Timeout { get; set; } = 5000;
        public int Port { get; set; } = Constants.Port.Interactive;
        public string ChannelName { get; set; } = "SCC Channel";
        public bool IsConnected => _channel?.IsOpen() == true;

        public IDictionary<comm.EMessageID, MessageHandler> MessageHandlers { get; private set; } = new Dictionary<comm.EMessageID, MessageHandler>();

        #endregion Public Properties

        #region C-Tor

        public Connection()
        {
            _netMan = new PacketNetworkManager( new RawTcpManager( TcpError ) );
            _channelFactory = new ChannelFactory( _netMan );
        }

        #endregion C-Tor

        #region Public Methods

        public async Task<Result> Connect()
        {
            return await ConnectInternal();
        }

        public void Disconnect()
        {
            if( _channel != null )
            {
                if ( _channel.IsOpen() )
                {
                    _channel.ShutDown();
                }

                _channel.UnregisterListener( MessageReceived );
                _channel = null;
            }
        }

        public void Send( comm.Message message )
        {
            try
            {
                _channel.Send( message );
            }
            catch ( CommunicationException ex )
            {
                TcpError( ex );
            }
        }

        public async Task<comm.Message> SendAwaitResponse( comm.Message message )
        {
            try
            {
                comm.Message response = null;
                await Task.Run( () => { response = _channel.SendAndWait( message, Timeout ); } );

                return response;
            }
            catch ( CommunicationException ex )
            {
                TcpError( ex );
            }

            return null;
        }

        #endregion Public Methods

        #region Private Methods

        private async Task<Result> ConnectInternal()
        {
            if ( IsConnected )
                return Result.Success;

            if ( !await CreateChannel() )
            {
                WriteLog( $"Scc did not respond to a connection request" );
                return Result.ChannelFailure;
            }

            if ( !await IsRequiredVersion() )
            {
                Disconnect();
                return Result.VersionFailure;
            }

            return Result.Success;
        }

        private async Task<bool> CreateChannel()
        {
            if ( _channel != null )
                return true;

            UriBuilder uriBuilder = new UriBuilder( "127.0.0.1" );
            uriBuilder.Port = Port;

            TcpAddress addr = new TcpAddress( uriBuilder.Uri );

            _channel = await _channelFactory.TryOpenChannel( ChannelName, addr, 1000 );
            _channel?.RegisterListener( MessageReceived );

            return _channel != null;
        }

        private async Task<bool> IsReady()
        {
            comm.SccCommandMsg msg = new comm.SccCommandMsg()
            {
                command = SccCommandIdIsReady
            };

            comm.SccCommandMsg response = await SendAwaitResponse( msg ) as comm.SccCommandMsg;

            return response?.command == SccCommandIdIsReady;
        }

        private async Task<bool> IsRequiredVersion()
        {
            comm.SccCommandMsg msg = new comm.SccCommandMsg()
            {
                command = SccCommandIdVersion
            };

            comm.SccVersion response = await SendAwaitResponse( msg ) as comm.SccVersion;

            if ( response == null )
            {
                WriteLog( $"Scc did not respond with a version number when requested. Ignoring." );
                return false;
            }

            Version encountered = new Version( (int)response.major, (int)response.minor, (int)response.build );

            if ( encountered != RequiredSccVersion )
            {
                WriteLog( $"Scc is version {encountered} but we require {RequiredSccVersion}." );
                return false;
            }

            return true;
        }

        private void MessageReceived( comm.Message message, IResponse responseGateway )
        {
            MessageHandler handler;
            if ( MessageHandlers.TryGetValue( message.GetID(), out handler ) )
            {
                System.Windows.Application.Current.Dispatcher.BeginInvoke( (Action)( () => { handler.Invoke( message ); } ) );
            }
            else
            {
                Debug.Print( $"Message ignored: {message.GetName()}" );
            }
        }

        private void TcpError( Exception obj )
        {
            Debug.Print( $"TCP Error: {obj}" );
        }

        #endregion Private Methods
    }
}
